/* $Id: log.c 1062 2010-04-23 05:58:05Z phrakt $ */
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <canopy/mem.h>
#include <canopy/string.h>
#include <canopy/queue.h>

#include "log.h"
#include "private.h"


extern char *__progname;


struct cnp_log_hdlr {
	cnp_loghdlr_t              hdlr_func;
	void                      *hdlr_arg;
	cnp_logmask_t              hdlr_mask;
	TAILQ_ENTRY(cnp_log_hdlr)  hdlr_link;
};



struct cnp_log_chan {
	char                       *ch_name;
	int                         ch_flags;
	cnp_logmask_t               ch_mask;

	/* a list of handlers for this channel */
	TAILQ_HEAD(, cnp_log_hdlr)  ch_hdlrs;

	cnp_rwlock_t                ch_lock;
};



const char *cnp_log_pri_names[] = {
	"trace",
	"debug",
	"info",
	"notice",
	"warning",
	"error",
	"error",
	"critical",
};




/*
 * cnp_log_open()
 *
 * Open a new log channel with identification string <ident> and flags <flags>.
 * The <mask> argument specifies the mask to use for message filtering.
 *
 * Returns a pointer to the created log channel on success, or NULL on failure.
 */
cnp_log_t*
cnp_log_open(const char *ident, int flags, cnp_logmask_t mask)
{
	cnp_log_t *chan;

	if ((chan = cnp_malloc(sizeof(*chan))) == NULL)
		return (NULL);

	chan->ch_flags = flags;
	chan->ch_mask = mask;
	TAILQ_INIT(&chan->ch_hdlrs);

	if (cnp_rwlock_init(&chan->ch_lock, NULL) == -1) {
		cnp_free(chan);
		return (NULL);
	}

	if ((chan->ch_name = cnp_strdup(ident)) == NULL) {
		cnp_log_close(chan);
		return (NULL);
	}

	return (chan);
}


/*
 * cnp_log_getmask()
 *
 */
void
cnp_log_getmask(cnp_log_t *chan, cnp_logmask_t *mask)
{
	*mask = chan->ch_mask;
}

/*
 * cnp_log_setmask()
 *
 */
void
cnp_log_setmask(cnp_log_t *chan, cnp_logmask_t mask)
{
	chan->ch_mask = mask;
}


/*
 * cnp_log_close()
 *
 * Close the log channel <chan> and release all resources associated to it.
 * Any subsequent calls will have undefined behaviour.
 */
void
cnp_log_close(cnp_log_t *chan)
{
	struct cnp_log_hdlr *hdlr;

	if (cnp_rwlock_wrlock(&chan->ch_lock) == -1)
		return;

	if (chan->ch_name != NULL)
		cnp_free(chan->ch_name);

	while (!TAILQ_EMPTY(&chan->ch_hdlrs)) {
		hdlr = TAILQ_FIRST(&chan->ch_hdlrs);
		TAILQ_REMOVE(&chan->ch_hdlrs, hdlr, hdlr_link);
		cnp_free(hdlr);
	}

	(void)cnp_rwlock_unlock(&chan->ch_lock);
	(void)cnp_rwlock_destroy(&chan->ch_lock);

	cnp_free(chan);
}

/*
 * cnp_log_addhdlr()
 *
 * Add the handler function <func> to the list of handlers registered on the
 * log channel <chan>.  The handler's associated log mask is specified in
 * <mask>, and the parameter <arg> will be passed to each invocation of <func>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_log_addhdlr(cnp_log_t *chan, cnp_logmask_t mask, cnp_loghdlr_t func,
    void *arg)
{
	struct cnp_log_hdlr *hdlr;

	if ((hdlr = cnp_malloc(sizeof(*hdlr))) == NULL)
		return (-1);

	hdlr->hdlr_mask = mask;
	hdlr->hdlr_func = func;
	hdlr->hdlr_arg = arg;

	if (cnp_rwlock_wrlock(&chan->ch_lock) == -1)
		return (-1);

	TAILQ_INSERT_TAIL(&chan->ch_hdlrs, hdlr, hdlr_link);

	(void)cnp_rwlock_unlock(&chan->ch_lock);

	return (0);
}

/*
 * cnp_log_rmhdlr()
 *
 * Remove the handler <func> from the list of registered handlers for the log
 * channel <chan>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
cnp_log_rmhdlr(cnp_log_t *chan, cnp_loghdlr_t func)
{
	int ret = 0;
	struct cnp_log_hdlr *hdlr;

	if (cnp_rwlock_wrlock(&chan->ch_lock) == -1)
		return (-1);

	TAILQ_FOREACH(hdlr, &chan->ch_hdlrs, hdlr_link) {
		if (hdlr->hdlr_func == func) {
			TAILQ_REMOVE(&chan->ch_hdlrs, hdlr, hdlr_link);
			break;
		}
	}

	(void)cnp_rwlock_unlock(&chan->ch_lock);

	if (hdlr != NULL)
		cnp_free(hdlr);
	else
		ret = -1;

	return (ret);
}

/*
 * cnp_log()
 *
 * Log a format-string message of priority <pri> through the log channel
 * <chan>.  Handlers registered with the channel will be called sequentially
 * with the formatted message and ancillary data.  The message format is
 * specified in <fmt>.
 */
void
cnp_log(cnp_log_t *chan, cnp_logpri_t pri, const char *fmt, ...)
{
	va_list vap;

	va_start(vap, fmt);
	cnp_vlog(chan, pri, fmt, vap);
	va_end(vap);
}

/*
 * cnp_vlog()
 *
 * Log a format-string message of priority <pri> through the log channel
 * <chan>.  Handlers registered with the channel will be called sequentially
 * with the formatted message and ancillary data.  The message format is
 * specified in <fmt>.
 */
void
cnp_vlog(cnp_log_t *chan, cnp_logpri_t pri, const char *fmt, va_list vap)
{
	int ret, saved_errno = 0;
	size_t len;
	char buf[1024], errbuf[64] = ": ";
	struct cnp_log_hdlr *hdlr;
	struct cnp_log_msg msg;

	/*
	 * Save the value of errno to avoid overwriting it in case another
	 * function fails.
	 */
	if (pri == CNP_LOG_ERRNO)
		saved_errno = errno;

	if (cnp_rwlock_rdlock(&chan->ch_lock) == -1)
		return;

	/*
	 * If the message is blocked at the source by the channel's mask or
	 * if there are no registered handlers, there is no point in formatting
	 * the message so we just exit silently.
	 */
	if (CNP_LOG_ISMASKED(chan->ch_mask, pri) ||
	    TAILQ_EMPTY(&chan->ch_hdlrs)) {
		(void)cnp_rwlock_unlock(&chan->ch_lock);
		return;
	}

	(void)gettimeofday(&msg.msg_time, NULL);

	if ((ret = vsnprintf(buf, sizeof(buf), fmt, vap)) == -1)
		return;

	if (pri == CNP_LOG_ERRNO) {
		/* skip the first two characters ": " of the buffer */
		ret = strerror_r(saved_errno, errbuf + 2, sizeof(errbuf) - 2);
		if (ret == 0) {
			len = strlcat(buf, errbuf, sizeof(buf));
			if (len >= sizeof(buf))
				len = sizeof(buf) - 1;
		}
	}

	msg.msg_string = buf;
	msg.msg_priority = pri;
	msg.msg_chan = chan;

	TAILQ_FOREACH(hdlr, &chan->ch_hdlrs, hdlr_link) {
		if (CNP_LOG_ISMASKED(hdlr->hdlr_mask, pri))
			continue;

		(*hdlr->hdlr_func)(&msg, hdlr->hdlr_arg);
	}

	(void)cnp_rwlock_unlock(&chan->ch_lock);
}
